namespace ConsoleApp1;

public class Fixed
{
    //fixed 语句可防止垃圾回收器重新定位可移动变量，并声明指向该变量的指针。 固定变量的地址在语句的持续时间内不会更改。 只能在相应的 fixed 语句中使用声明的指针。 声明的指针是只读的，无法修改：
    public static void t1()
    {
        unsafe
        {
            byte[] bytes = { 1, 2, 3 };
            fixed (byte* pointerToFirst = bytes)
            {
                Console.WriteLine($"The address of the first array element: {(long)pointerToFirst:X}.");
                Console.WriteLine($"The value of the first array element: {*pointerToFirst}.");
            }
        }
        // Output is similar to:
        // The address of the first array element: 2173F80B5C8.
        // The value of the first array element: 1.
    }


    //在给定的C#代码中，UnsafeFixed方法使用了fixed语句块来固定一个数组的地址，并且通过指针来访问数组的元素。在这个例子中，数组a的第一个元素是10，第二个元素是20。
    // 
    // 让我们来解释为什么第二次打印的结果是20：
    // 
    // fixed语句块用于将一个托管对象的地址固定在内存中，以便在不受垃圾回收器影响的情况下使用指针来访问这个对象。在这个例子中，fixed语句块固定了数组a的地址。
    // 
    // 通过fixed (int* p = &a[0])，我们将数组a的第一个元素的地址赋给了指针p。
    // 
    // 接着，我们将指针p的值赋给了另一个指针p2，这样p2也指向了数组a的第一个元素。
    // 
    // 然后通过Console.WriteLine(*p2)打印了p2指向的值，也就是数组a的第一个元素，因此第一次打印结果是10。
    // 
    // 接着，我们对p2进行了加一操作，即p2 += 1，这使得p2指向了数组a的第二个元素。
    // 
    // 最后通过Console.WriteLine(*p2)打印了p2指向的值，也就是数组a的第二个元素，因此第二次打印结果是20。
    // 
    // 因此，第二次打印结果是20，这是因为我们通过指针p2访问了数组a的第二个元素。
    public static void UnsafeFixed()
    {
        int[] a = { 10, 20, 30, 40 };

        unsafe
        {
            //在C#中，&符号用于获取变量的内存地址，这在指针操作中非常有用。当你在C#中使用&符号时，它会返回变量的内存地址，这个地址可以被赋给指针变量，以便对该变量进行指针操作。
            fixed (int* p = &a[0])
            {
                int* p2 = p;
                Console.WriteLine(*p2);
                //在C#中，指针是一种特殊的数据类型，它存储了内存地址。当你对指针进行加法运算时，实际上是在当前地址的基础上增加了一定的偏移量。这个偏移量是根据指针所指向的数据类型的大小来计算的。
                // 
                // 在你提供的代码中，p是一个指向数组a第一个元素的指针，p2是指向p的指针。当你执行p2 += 1时，p2的值会增加，指向p所指向的内存地址的下一个位置。
                // 
                // 在C#中，指针的加法操作会根据指针所指向的数据类型的大小来进行偏移。在这个例子中，p和p2都是指向int类型的数据，而int通常占据4个字节（32位系统）或8个字节（64位系统）。因此，当你执行p2 += 1时，p2的值会增加4（或8，取决于系统）个字节，这样它就指向了数组a的第二个元素的位置。
                // 
                // 这就是为什么在你的代码中，通过p2 += 1后，p2就能够指向数组的第二个元素。希望这样能够更清楚地解释为什么 p2 += 1 能够指向数组的第二个元素。
                p2 += 1;

                //在C#中，指针操作是受到严格的类型安全和边界检查的。当你执行 p2 += 100 时，如果这个操作没有导致越界访问，程序不会崩溃。然而，如果 p2 指向的是一个数组，并且你执行了 p2 += 100，那么 p2 将指向数组中当前位置往后 100 个元素的内存地址。
                // 
                // 如果你尝试输出 *p2 的值，这时输出的是0，这是因为你访问了一个超出数组边界的内存地址。在这种情况下，C#并不会报错，而是会返回该内存地址上的默认值，对于整型来说，默认值是0。
                // 
                // 这也是指针操作中需要非常小心的原因之一。在C#中，使用指针需要确保不会越界访问，否则可能会导致程序出现意想不到的行为。
                // 
                // 因此，当执行 p2 += 100 后，p2 指向的位置已经远远超出了数组的边界，因此访问该位置的值会得到默认值，即0。
                //输出的是0
                // p2 += 999;

                Console.WriteLine(*p2);

                Console.WriteLine("--------");
                Console.WriteLine(*p);
                // Dereferencing p and incrementing changes the value of a[0] ...
                *p += 1;
                Console.WriteLine(*p);
                *p += 1;
                Console.WriteLine(*p);
            }
        }
    }
}